﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace WinApp.Common
{
    public class SocketListener : IDisposable
    {
        private int mPort;

        private Socket mListener;

        private bool mDisposed;

        public const int DEFAULT_LISTEN_BACKLOG = 20;

        public int ListenBacklog { get; set; }

        private ProtocolType mProtocolType;

        private Dictionary<SocketConnection, Thread> mConnectionDictionary;

        public ReadOnlyCollection<SocketConnection> Connections 
        {
            get
            {
                return new ReadOnlyCollection<SocketConnection>(mConnectionDictionary.Keys.ToList());
            }
        }

        public SocketListener(string ip, int port, ProtocolType protocolType)            
        {
            this.mPort = port;

            this.mProtocolType = protocolType;
            this.ListenBacklog = DEFAULT_LISTEN_BACKLOG;
            this.mConnectionDictionary = new Dictionary<SocketConnection, Thread>();            
        }

        #region Dispose
        ~SocketListener()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected void Dispose(bool disposing)
        {
            if (mDisposed)
            {
                return;
            }

            try
            {
                if (disposing)
                {
                    if (mListener != null)
                    {
                        mListener.Close();
                        mListener.Dispose();
                        mListener = null;
                    }
                    
                }
            }
            finally
            {
                mDisposed = true;
            }
        } 
        #endregion

        public Action<SocketConnection, string> ReceivedNoticeAction;

        public Action<ReadOnlyCollection<SocketConnection>> SocketConnectionChanged;

        public Thread FindConnectionThread(SocketConnection connection)
        {
            if (mConnectionDictionary.ContainsKey(connection))
            {
                return mConnectionDictionary[connection];
            }

            return null;
        }

        public void Start()
        {
            if (mListener == null)
            {
                mListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, mProtocolType);
            }

            try
            {
                mListener.Bind(new IPEndPoint(IPAddress.Any, mPort));
                mListener.Listen(this.ListenBacklog);

                while (true)
                {
                    Socket connection = mListener.Accept();

                    //start new thread deal with current connection
                    SocketConnection socketConnection = new SocketConnection(connection);
                    if (ReceivedNoticeAction != null)
                    {
                        socketConnection.ReceiveNoticeAction = (sc, receiveData) => {
                            ReceivedNoticeAction(sc, receiveData);
                        };
                    }                    

                    Thread thread = new Thread((paramObj) => {
                        socketConnection.HandleReceive();
                    });
                    thread.Name = connection.RemoteEndPoint.ToString();
                    thread.Start();

                    mConnectionDictionary.Add(socketConnection, thread);
                    CallSocketConnectionChangedAction();
                }
            }
            catch
            {

            }
        }

        public bool TerminateConnection(string clientIP)
        {
            SocketConnection sc = mConnectionDictionary.Keys.FirstOrDefault(x => x.Connection.RemoteEndPoint.ToString().Equals(clientIP));
            if (sc != null)
            {
                try
                {
                    Thread hostThread = this.FindConnectionThread(sc);
                    if (hostThread != null && hostThread.IsAlive)
                    {
                        //hostThread.Join();
                        hostThread.Abort();
                    }

                    if (sc.Connection != null)
                    {
                        try
                        {
                            sc.Connection.Shutdown(SocketShutdown.Both);
                            sc.Connection.Close();                            
                        }
                        catch
                        {
                            
                        }   
                    }                    

                    mConnectionDictionary.Remove(sc);
                    CallSocketConnectionChangedAction();

                    return true;
                }
                catch
                {
                    return false;
                }
                finally
                {

                }              
            }

            return false;
        }

        private void CallSocketConnectionChangedAction()
        {
            if (SocketConnectionChanged != null)
            {
                SocketConnectionChanged(this.Connections);
            }
        }
    }
}
